package com.huifanedu.approval.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.huifanedu.approval.configuration.CustomProcessDiagramGenerator;
import com.huifanedu.approval.constant.FlowableConstant;
import com.huifanedu.approval.entity.FlowableApprovalStatistics;
import com.huifanedu.approval.entity.FlowableCatalogStatistics;
import com.huifanedu.approval.enums.ApprovalExceptionEnum;
import com.huifanedu.approval.factory.model.ApprovalTaskResponse;
import com.huifanedu.approval.handler.ProcessStartHandler;
import com.huifanedu.approval.listener.ProcInsListHandler;
import com.huifanedu.approval.mapper.FlowableCustomerMapper;
import com.huifanedu.approval.model.params.ProcessInstanceParam;
import com.huifanedu.approval.model.params.ProcessQueryParam;
import com.huifanedu.approval.service.FlowableInstanceService;
import com.huifanedu.kernel.core.util.SpringContextHolder;
import com.huifanedu.kernel.core.util.StringUtil;
import com.huifanedu.kernel.model.exception.BusinessException;
import com.huifanedu.kernel.model.pager.PageData;
import com.huifanedu.kernel.model.response.HttpResult;
import com.huifanedu.kernel.redis.entity.CacheUserInfo;
import com.huifanedu.kernel.redis.sercive.CacheService;
import org.apache.commons.io.IOUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.cmd.CustomSqlExecution;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.HistoricProcessInstanceQueryProperty;
import org.flowable.engine.impl.cmd.AbstractCustomSqlExecution;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.task.api.Task;
import org.flowable.task.service.impl.TaskQueryProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * <b>功能名：FlowableInstanceServiceImpl</b><br>
 * <b>说明：</b><br>
 * <b>著作权：</b> Copyright (C) 2021 HUIFANEDU  CORPORATION<br>
 * <b>修改履历：
 *
 * @author 2021-03-25 jiabing
 */
@Service
@Transactional(rollbackFor = {Exception.class})
public class FlowableInstanceServiceImpl extends BaseFlowableServiceImpl implements FlowableInstanceService {

    private static final Logger log = LoggerFactory.getLogger(FlowableInstanceServiceImpl.class);

    @Autowired
    private CacheService cacheService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    protected HistoryService historyService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private ManagementService managementService;

    @Autowired
    private ProcessEngineConfiguration processEngineConfiguration;

    @Override
    public ProcessInstance start(ProcessInstanceParam processInstanceRequest) {
        String processDefinitionId = processInstanceRequest.getProcessDefinitionId();
        String handlerName = processInstanceRequest.getHandlerName();
        String businessKey = processInstanceRequest.getBusinessKey();
        /**
         * 特殊需求可传handlerName  保留扩展流程启动方法，
         */
        ProcessStartHandler processStartHandler = SpringContextHolder.getBean(handlerName);
        if (BeanUtil.isNotEmpty(processStartHandler)) {
            //流程业务主键，自定义handler可以将数据保存并生成新的业务主键也可以
            //可以扩展流程初始化前的逻辑
            //TODO 当前业务在handler里面初始化多实例配置
            businessKey = processStartHandler.init(processInstanceRequest);

        }
        if (ObjectUtil.isEmpty(processDefinitionId)) {
            throw new BusinessException(ApprovalExceptionEnum.FLOWABLE_PROCESSDEF_ID_IS_NULL);
        }
        //获取当前登陆用户
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        CacheUserInfo userInfo = cacheService.getCacheUserInfo(request);
        if (userInfo == null || StringUtil.isEmpty(userInfo.getUsrId())) {
            throw new BusinessException(ApprovalExceptionEnum.APPROVAL_USER_MUST_LOGIN);
        }
        String userId = userInfo.getUsrId();
        //流程参数
        Map<String, Object> formMap = processInstanceRequest.getFormMap();
        Map<String, Object> variables = formMap;
        //设置流程发起人
        Authentication.setAuthenticatedUserId(userId);
        //构建流程实例
        ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder();
        processInstanceBuilder.processDefinitionId(processDefinitionId);
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
        if (ObjectUtil.isEmpty(processDefinition)) {
            throw new BusinessException(ApprovalExceptionEnum.FLOWABLE_PROCESSDEF_ID_IS_NULL);
        }
        //如果流程名称为空  则从流程定义表中查询流程名称
        if (StringUtil.isEmpty(processInstanceRequest.getProcessName())) {
            processInstanceRequest.setProcessName(processDefinition.getName());
        }
        // 流程实例标题
        processInstanceBuilder.name(processInstanceRequest.getProcessName());

        if (ObjectUtil.isNotEmpty(processStartHandler)) {
            //初始化流程变量
            variables = processStartHandler.initVariables(processInstanceRequest, processDefinition);
            if (ObjectUtil.isEmpty(variables)) {
                variables = new HashMap<>(16);
            }
            variables.put(FlowableConstant.BUSINESS_KEY, businessKey);
        }
        // 业务key
        processInstanceBuilder.businessKey(businessKey);
        processInstanceBuilder.variables(variables);
        ProcessInstance instance = processInstanceBuilder.start();
        String processInstanceId = instance.getProcessInstanceId();
        //启动完成业务处理
        if (ObjectUtil.isNotEmpty(processStartHandler)) {
            List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
            processStartHandler.afterComplete(processInstanceRequest, processDefinition, instance, tasks, variables);
        }
        return instance;
    }

    @Override
    public ProcessInstance getProcessInstanceById(String processInstanceId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (ObjectUtil.isEmpty(processInstance)) {
            throw new BusinessException(ApprovalExceptionEnum.FLOWABLE_PROCESSDEF_ID_IS_NULL);
        }
        return processInstance;
    }

    /**
     * 抄送给我
     *
     * @param requestParams
     * @return
     */
    @Override
    public HttpResult<PageData<ApprovalTaskResponse>> ccToMe(ProcessQueryParam requestParams) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        CacheUserInfo user = cacheService.getCacheUserInfo(request);
        requestParams.setCcUserId(user.getUsrId() == null ? "null" : user.getUsrId());
        HistoricProcessInstanceQuery query = createHistoricProcessInstanceQuery(requestParams);
        return this.pageList(requestParams, query, ProcInsListHandler.class, taskSortMap, HistoricProcessInstanceQueryProperty.START_TIME);
    }

    /**
     * 我发起的流程
     *
     * @param requestParams
     * @return
     */
    @Override
    public HttpResult<PageData<ApprovalTaskResponse>> startByMe(ProcessQueryParam requestParams) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        CacheUserInfo user = cacheService.getCacheUserInfo(request);
        requestParams.setStartUser(user.getUsrId());
        HistoricProcessInstanceQuery query = createHistoricProcessInstanceQuery(requestParams);
        if (StringUtil.isEmpty(requestParams.getOrder())) {
            requestParams.setOrder("startTime");
        }
        return this.pageList(requestParams, query, ProcInsListHandler.class, taskSortMap, TaskQueryProperty.CREATE_TIME);
    }

    @Override
    public boolean setCcUserReaded(String userId, String processInstanceId) {
        ProcessQueryParam param = new ProcessQueryParam();
        param.setCcUserId(userId);
        param.setProcessInstanceId(processInstanceId);
        if (StringUtil.isEmpty(processInstanceId)) {
            throw new BusinessException(ApprovalExceptionEnum.APPROVAL_ADD_FORMDATA_CC_USER_PROCESS_NULL);
        }
        HistoricProcessInstanceQuery query = this.createHistoricProcessInstanceQuery(param);
        List<HistoricProcessInstance> processInstances = query.list();
        if (processInstances == null || processInstances.size() == 0) {
            throw new BusinessException(ApprovalExceptionEnum.APPROVAL_ADD_FORMDATA_CC_USER_PROCESS_NULL);
        }
        HistoricProcessInstance processInstance = processInstances.get(0);
        if (processInstance.getEndTime() == null) {
            runtimeService.setVariable(processInstanceId, userId, FlowableConstant.PROCESS_VARIABLE_APPROVAL_CC_READ);
        } else {
            //已完成
            CustomSqlExecution<FlowableCustomerMapper, Integer> customSqlExecution = new AbstractCustomSqlExecution<FlowableCustomerMapper, Integer>(FlowableCustomerMapper.class) {
                @Override
                public Integer execute(FlowableCustomerMapper customMapper) {
                    return customMapper.updateHisProcessVariable(processInstanceId, userId, FlowableConstant.PROCESS_VARIABLE_APPROVAL_CC_READ);
                }
            };
            Integer updateCount = managementService.executeCustomSql(customSqlExecution);
            if (updateCount == null || updateCount > 1) {
                log.info("更新历史流程表抄送状态为已读异常:userId={},processInstanceId={}", userId, processInstanceId);
            }
        }
        return true;
    }

    @Override
    public HttpResult<List<FlowableCatalogStatistics>> flowableCatalogStatistics(String parentCatalogId) {
        CustomSqlExecution<FlowableCustomerMapper, List<FlowableCatalogStatistics>> customSqlExecution = new AbstractCustomSqlExecution<FlowableCustomerMapper, List<FlowableCatalogStatistics>>(FlowableCustomerMapper.class) {
            @Override
            public List<FlowableCatalogStatistics> execute(FlowableCustomerMapper customMapper) {
                return customMapper.flowableCatalogStatistics(parentCatalogId);
            }
        };
        List<FlowableCatalogStatistics> res = managementService.executeCustomSql(customSqlExecution);
        return HttpResult.success(res);
    }

    @Override
    public HttpResult<List<FlowableApprovalStatistics>> flowableApprovalStatistics(String parentCatalogId) {
        CustomSqlExecution<FlowableCustomerMapper, List<FlowableApprovalStatistics>> customSqlExecution = new AbstractCustomSqlExecution<FlowableCustomerMapper, List<FlowableApprovalStatistics>>(FlowableCustomerMapper.class) {
            @Override
            public List<FlowableApprovalStatistics> execute(FlowableCustomerMapper customMapper) {
                return customMapper.flowableApprovalStatistics(parentCatalogId);
            }
        };
        List<FlowableApprovalStatistics> res = managementService.executeCustomSql(customSqlExecution);
        return HttpResult.success(res);
    }

    @Override
    public HttpResult<String> image(String processInstanceId) {
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            throw new BusinessException(ApprovalExceptionEnum.FLOWABLE_PROCESSDEF_ID_IS_NULL);
        }
        ProcessDefinition pde = repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
        if (pde == null || !pde.hasGraphicalNotation()) {
            throw new BusinessException(ApprovalExceptionEnum.FLOWABLE_PROCESSDEF_ID_IS_NULL);
        }
        List<String> highLightedFlows = new ArrayList<>();
        List<String> highLightedActivities = new ArrayList<>();
        List<HistoricActivityInstance> allHistoricActivityIntances = historyService
                .createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
        allHistoricActivityIntances.forEach(historicActivityInstance -> {
            if (BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW.equals(historicActivityInstance.getActivityType())) {
                highLightedFlows.add(historicActivityInstance.getActivityId());
            } else {
                highLightedActivities.add(historicActivityInstance.getActivityId());
            }
        });

        List<String> runningActivitiIdList = null;
        // 流程已结束
        if (processInstance != null && processInstance.getEndTime() != null) {
            runningActivitiIdList = Arrays.asList();
        } else {
            runningActivitiIdList = runtimeService.getActiveActivityIds(processInstanceId);
        }

        BpmnModel bpmnModel = repositoryService.getBpmnModel(pde.getId());
        CustomProcessDiagramGenerator diagramGenerator = (CustomProcessDiagramGenerator) processEngineConfiguration.getProcessDiagramGenerator();
        InputStream resource = diagramGenerator.generateCustomDiagram(bpmnModel, "png", highLightedActivities,
                runningActivitiIdList, highLightedFlows, processEngineConfiguration.getActivityFontName(),
                processEngineConfiguration.getLabelFontName(), processEngineConfiguration.getAnnotationFontName(),
                processEngineConfiguration.getClassLoader(), 1.0, true);
        byte[] bytes = new byte[0];
        try {
            bytes = IOUtils.toByteArray(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String encoded = Base64.getEncoder().encodeToString(bytes);
        return HttpResult.success(encoded);
    }


    protected HistoricProcessInstanceQuery createHistoricProcessInstanceQuery(ProcessQueryParam requestParams) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
        if (StringUtil.isNotEmpty(requestParams.getProcessInstanceId())) {
            query.processInstanceId(requestParams.getProcessInstanceId());
        }
        if (StringUtil.isNotEmpty(requestParams.getBuissessKey())) {
            query.processInstanceBusinessKeyLike(requestParams.getBuissessKey());
        }
        if (StringUtil.isNotEmpty(requestParams.getProcessInstanceName())) {
            query.processInstanceNameLike("%" + requestParams.getProcessInstanceName() + "%");
        }
        if (StringUtil.isNotEmpty(requestParams.getProcessDefId())) {
            query.processDefinitionId(requestParams.getProcessDefId());
        }
        if (StringUtil.isNotEmpty(requestParams.getStartUser())) {
            query.startedBy(requestParams.getStartUser());
        }
        if (StringUtil.isNotEmpty(requestParams.getApprovalCatalogId())) {
            query.variableValueEquals(FlowableConstant.PROCESS_VARIABLE_APPROVAL_CATALOG_ID, requestParams.getApprovalCatalogId());
        }
        if (StringUtil.isNotEmpty(requestParams.getApprovalInfoId())) {
            query.variableValueEquals(FlowableConstant.PROCESS_VARIABLE_APPROVAL_INFO_ID, requestParams.getApprovalInfoId());
        }
        if (StringUtil.isNotEmpty(requestParams.getCcUserId())) {
            query.variableValueLike(FlowableConstant.PROCESS_VARIABLE_APPROVAL_CC_USER_IDS_ID, "%" + requestParams.getCcUserId() + "%");
        }
        if (ObjectUtil.isNotNull(requestParams.getFinished())) {
            if (requestParams.getFinished() == 1) {
                query.finished();
            } else {
                query.unfinished();
            }
        }
        if (StringUtil.isNotEmpty(requestParams.getIsCcToMeReaded())) {
            if (FlowableConstant.PROCESS_VARIABLE_APPROVAL_CC_READ.equals(requestParams.getIsCcToMeReaded())) {
                query.variableValueEquals(requestParams.getCcUserId(), FlowableConstant.PROCESS_VARIABLE_APPROVAL_CC_READ);
            } else {
                query.variableValueEquals(requestParams.getCcUserId(), FlowableConstant.PROCESS_VARIABLE_APPROVAL_CC_NO_READ);
            }
        }
        return query;
    }
}